/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> uls_util.c </file>
  <brief>
    The utility routines in ULS.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, 2011.
  </author>
*/

#include "common.h"
#include "uls_util.h"
#include "istr_fd.h"
#include "csz_stream.h"
#include "uls_istream.h"
#include "uls_log.h"

int
uls_mktemp(char* filepath, int filepath_siz)
{
	int fd_out;

#ifdef WIN32
	uls_snprintf(filepath, filepath_siz,
		"C:\\Temp\\ULStmpfile_%u_%u", (unsigned int) GetCurrentThreadId(), rand());
#else
	uls_snprintf(filepath, filepath_siz,
		"/tmp/ULStmpfile_%u_%u", getpid(), rand());
#endif
	if ((fd_out = uls_open(filepath,
			O_CREAT | O_TRUNC | O_WRONLY | _O_BINARY, 0644)) < 0) {
		return -1;
	}

	return fd_out;
}

int
uls_movefile(char* fpath1, char* fpath2)
{
#ifdef WIN32
	if (!MoveFileExA(fpath1, fpath2,
		MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
		return -1;
	}
#else
	char buff[2048];
	uls_snprintf(buff, sizeof(buff), "/bin/mv -f %s %s", fpath1, fpath2);
	if (system(buff) != 0) {
		return -1;
	}
#endif
	return 0;
}

#ifdef WIN32
int
uls_file_exist(char *path)
{
	HANDLE  hFile;

	hFile = CreateFileA(
		path,
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);

	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	CloseHandle(hFile);

	return 1;
}
#else
int
uls_file_exist(char* path)
{
	struct stat statbuff;

	if (path==NULL || *path=='\0') return 0;

	if (stat(path, &statbuff) < 0) {
		if (errno==ENOENT) return 0;
		else return 0;
	}

	return 1;
}
#endif

void*
uls_bi_search(uls_char_t* kwrd, void** ptr_ary, int n_ptr_ary,
	uls_bi_comp_t comp_func)
{
	int   low, high, mid, cond;
	void  *e;

	low = 0;
	high = n_ptr_ary - 1;

	while (low <= high) {
		mid = (low + high) / 2;
		e = ptr_ary[mid];

		if ((cond=comp_func(e, kwrd))<0) {
			low = mid + 1;
		} else if (cond > 0) {
			high = mid - 1;
		} else {
			return e;
		}
	}

	return NULL;
}

int
is_octal_char(char ch)
{
	if (ch >= '0' && ch < '8') return 1;
	return 0; // including '\0'
}

int
is_hexa_char(char ch)
{
	ch = toupper(ch);

	if ((ch>='A' && ch<='F') || isdigit(ch))
		return 1;

	return 0;
}

uls_char_t
read_hexa_char(uls_char_t* ptr)
{
	unsigned n, ch_val = 0;
	uls_char_t ch;

	if ((ch=*ptr) == '0') {
		if ((ch=*++ptr)=='x') ++ptr;
	}

	for ( ; (ch=*ptr)!='\0'; ptr++) {
		if (isalpha(ch)) {
			if ((ch=tolower(ch)) > 'f') break;
			n = 10 + ch - 'a';
		} else if (isdigit(ch)) {
			n = ch - '0';
		} else {
			break;
		}

		ch_val = 16 *ch_val + n;
	}

	return (uls_char_t) ch_val;
}

int
is_int_number(char* lptr)
{
	char  *lptr0, ch;

	if (*lptr == '-') {
		++lptr;
	}

	lptr0 = lptr;
	for ( ; (ch=*lptr)!='\0'; lptr++) {
		if (!isdigit(ch)) return 0;
	}

	return (int) (lptr - lptr0);
}

unsigned int
skip_atou(char** p_lptr)
{
	char  *lptr = *p_lptr;
	char ch;
	unsigned int     n;

	uls_assert(isdigit(*lptr));
	n = *lptr++ - '0';

	for ( ; isdigit(ch=*lptr); lptr++) {
		// ch >= '0' && ch <= '9'
		n = n*10 + (ch - '0');
	}

	*p_lptr = lptr;
	return n;
}

unsigned int
skip_atox(char ** p_lptr)
{
	char *lptr = *p_lptr;
	char ch;
	unsigned int     n = 0;

	for ( ; ; lptr++) {
		ch = *lptr;

		if (ch >= '0' && ch <= '9') {
			n = n*16 + (ch - '0');
		} else if (isalpha(ch) && (ch=toupper(ch)) >= 'A' && ch <= 'F') {
			n = n*16 + 10 + (ch - 'A');
		} else {
			break;
		}
	}

	*p_lptr = lptr;
	return n;
}

int
get_range_atou(char** p_lptr, unsigned int *p_i1, unsigned int* p_i2)
{
	char  *lptr = *p_lptr;
	unsigned int i1, i2;

	uls_assert(isdigit(*lptr));

	if (lptr[0] == '0' && lptr[1] == 'x') {
		lptr += 2;
		i1 = skip_atox(&lptr);

		if (*lptr=='-') {
			++lptr;
			if ((i2=skip_atox(&lptr)) < i1) {
				*p_i1 = i1;
				*p_i2 = i2;
				return -1;
			}
		} else {
			i2 = i1;
		}

	} else {
		i1 = skip_atou(&lptr);

		if (*lptr=='-') {
			i2 = 0;
			if (!isdigit(*++lptr) || (i2=skip_atou(&lptr)) < i1) {
				*p_i1 = i1;
				*p_i2 = i2;
				return -1;
			}
		} else {
			i2 = i1;
		}
	}

	*p_i1 = i1;
	*p_i2 = i2;
	*p_lptr = lptr;

	return 1;
}

char*
skip_space_chars(char* lptr)
{
	register int ch;

	for ( ; (ch=*lptr)==' ' || ch=='\t'; lptr++)
		/* nothing */;
	return lptr;
}

char*
uls_filename(const char *filepath)
{
	const char *ptr, *fname;

	ptr = uls_strchr_r(filepath, '/');

	if (ptr != NULL) fname = ptr + 1;
	else fname = filepath;

	return (char *) fname;
}

int
uls_get_column_int(char* line, int* ptr_i)
{
	int   n, i=*ptr_i;
	int   minus=0, ch;

	for ( ; (ch=line[i])==' ' || ch=='\t'; i++)
		/* NOTHING */;

	if (line[i] == '-') {
		minus = 1;
		++i;
	}

	if ( !isdigit(ch=line[i]) ) {
		err_log("%s: NOT number", __FUNCTION__);
		return -1;
	} else {
		n = ch - '0';
		++i;
	}

	for ( ; isdigit(ch=line[i]); i++) {
		// ch >= '0 && ch <= '9'
		n = n*10 + (ch - '0');
	}

	if (minus) n = -n;
	*ptr_i = i;

	return n;
}

int
uls_get_column_words(char *line, char delim_ch, char** args, int n_args)
{
	char  *lptr=line, *lptr0, ch;
	int   k, j;

	for (k=0; k<n_args; ) {
		if (delim_ch == ' ') {
			for ( ; (ch=*lptr)==' ' || ch=='\t'; lptr++)
				/* NOTHING */;
		}

		for (lptr0=lptr; ; lptr++) {
			if ((ch=*lptr)=='\0') {
				args[k++] = lptr0;
				for (j=k; j<n_args; j++) {
					args[j] = lptr;
				}
				return k;
			}

			if (ch == delim_ch || (delim_ch == ' ' && ch=='\t')) {
				*lptr++ = '\0';
				args[k++] = lptr0;
				break;
			}
		}
	}

	return k;
}

int
uls_encode_utf8(uls_uch_t uch, uls_char_t* buff)
{
	int rc;

	if (uch <= 0x7F) {
		buff[0] = uch;
		rc = 1;
	} else if (uch <= 0x07FF) {
		buff[0] = 0xC0 | (uch >> 6);
		buff[1] = 0x80 | (0x3F & uch);
		rc = 2;
	} else if (uch <= 0xFFFF) {
		buff[0] = 0xE0 | (uch >> 12);
		buff[1] = 0x80 | (0x3F & (uch >> 6));
		buff[2] = 0x80 | (0x3F & uch);
		rc = 3;
	} else if (uch <= 0x10FFFF) {
		buff[0] = 0xF0 | (uch >> 18);
		buff[1] = 0x80 | (0x3F & (uch >> 12));
		buff[2] = 0x80 | (0x3F & (uch >> 6));
		buff[3] = 0x80 | (0x3F & uch);
		rc = 4;
	} else {
		rc = 0;
	}

	return rc;
}

int
uls_decode_utf8(uls_char_t *lptr, uls_uch_t *p_val)
{
	uls_char_t ch, ch_mask, ch_ary[3] = {0x20, 0x10, 0x08};
	uls_uch_t val;
	int  n, i;

	if (((ch=*lptr++) & 0x80) == 0) {
		*p_val = ch;
		return 1;
	}

	if ((ch & 0x40) == 0) {
		return -1;
	}

	for (n=0; ; n++) {
		if (n>=3) return -2;
		if ((ch_ary[n] & ch)==0) break;
	}

	++n; // n-bytes followed additionally

	ch_mask = (1 << (6-n)) - 1;
	val = ch & ch_mask;

	ch_mask = (1 << 6) - 1;
	for (i=0; i<n; i++) {
		ch = *lptr++;
		if ((ch & 0xC0) != 0x80) {
			return -3;
		}
		val = (val << 6) | (ch & ch_mask);
	}

	++n; // n-bytes constitute a multibytes uls_char_t.
	*p_val = val;

	return n;
}

int
uls_dec_utf8(uls_char_t *lptr, uls_char_t *lptr_end, uls_uch_t *p_val)
{
	uls_char_t buff[UTF8_CH_MAXLEN+1];
	uls_char_t *bufptr;
	int     i, rc;
	uls_uch_t  uch;

	if (lptr_end == NULL) lptr_end = lptr + uls_strlen(lptr);
	if (lptr >= lptr_end)
		err_panic("%s: assertion failed: lptr < lptr_end", __FUNCTION__);

	if ((rc = (int) (lptr_end - lptr)) >= UTF8_CH_MAXLEN) {
		bufptr = lptr;
	}  else {
		for (i=0; i<rc; i++) buff[i] = lptr[i];
		buff[i] = '\0';
		bufptr = buff;
	}

	if ((rc=uls_decode_utf8(bufptr, &uch)) <= 0) {
		rc = 1;
		uch = bufptr[0];
	}

	if (p_val != NULL) *p_val = uch;
	return rc;
}

uls_char_t*
uls_skip_multiline_comment(uls_char_t* lptr, uls_char_t* lptr_end, int* num_nl)
{
	int  ch, prev_ch, n=0;
/*
 * ret-val == NULL : NOT FOUND, the end of c-style comment
 * ret-val == next-uls_char_t behind the end of comment area
 */
	for (prev_ch=-1; ; lptr++) {
		if (lptr == lptr_end) {
			lptr = NULL;
			break;
		}

		if ((ch=*lptr) == '\n') ++n;

		if (prev_ch == '*' && ch=='/') {
			++lptr;
			break;
		}

		prev_ch = ch;
	}

	*num_nl = n;
	return lptr;
}

uls_char_t*
uls_skip_singleline_comment(uls_char_t* lptr, uls_char_t* lptr_end)
{
	int  ch;
/* ret-val == NULL 0 : error */
/* ret-val == 0 : c++-style comment completed */

	for ( ; lptr != lptr_end; lptr++) {
		ch = *lptr;
		if (ch =='\n') return lptr + 1; 
	}

	return NULL;
}

int
wchar_lit_analyzer(
	istr_fd_t *fd_src, uls_char_t* quote_str, csz_str_t *ss_dst, uls_literal_t* lit, int* p_lno)
{
	int  stat = 0;
	uls_char_t *lptr, *lptr_end;
	int   len, len_needed;
	int   n_lfs = 0;
	wchar_t wch;

	lptr = fd_src->raw_bufptr;
	lptr_end = lptr + fd_src->raw_buf_bytes;

	len = csz_length(ss_dst);
	len_needed = sizeof(wchar_t) + 1; // +1 == '"'

	/* writing escape chars to ss_dst */
	for ( ; ; ) {
		if (lptr_end < lptr + len_needed) {
			fd_src->raw_bufptr = lptr;
			fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

			if (refill_fd_src(fd_src, len_needed) < 0) {
				return -1;
			}

			stat = 1;

			lptr = fd_src->raw_bufptr;
			lptr_end = lptr + fd_src->raw_buf_bytes;

			if (fd_src->raw_buf_bytes < len_needed) {
				err_log("unterminated literal-string");
				return -1;
			}
		}

		wch = * (wchar_t *) lptr;
		csz_append(ss_dst, lptr, sizeof(wchar_t));
		lptr += sizeof(wchar_t);

		if (wch == L'\n') {
			++n_lfs;
		}

		if (wch == L'\0') {
			if (*lptr != '"') {
				err_log("unterminated literal-string");
				return -1;
			}
			++lptr;
			break;
		}
	}

	fd_src->raw_bufptr = lptr;
	fd_src->raw_buf_bytes = (int) (lptr_end - lptr);
	lit->len_text = csz_length(ss_dst) - len;
	*p_lno += n_lfs;

	return stat;
}

const char*
uls_strchr_r(const char* lptr, char ch)
{
	int i, len = strlen(lptr);

	for (i=len-1; i >= 0; i--) {
		if (lptr[i] == ch) return lptr + i;
	}
	return NULL;
}

int
uls_str_tolower(char* src, char *dst, int len)
{
	int i;
	char ch;

	if (len < 0) {
		for(i=0; (ch=src[i])!='\0'; i++ )
			dst[i] = (uls_char_t) tolower(ch);
	} else {
		for(i=0; i<len && (ch=src[i])!='\0'; i++)
			dst[i] = (uls_char_t) tolower(ch);
	}

	dst[i] = '\0';
	return i;
}

int
uls_str_toupper(char* src, char *dst, int len)
{
	int i;
	char ch;

	if (len < 0) {
		for(i=0; (ch=src[i])!='\0'; i++)
			dst[i] = (uls_char_t) toupper(ch);
	} else {
		for(i=0; i<len && (ch=src[i])!='\0'; i++)
			dst[i] = (uls_char_t) toupper(ch);
	}

	dst[i] = '\0';
	return i;
}

int
uls_strcasecmp(const char* str1, const char* str2)
{
#ifdef HAVE_STRCASECMP
	return strcasecmp(str1, str2);
#else
	int  ch1, ch2;
	int  i;

	for (i=0; ; i++) {
		ch1 = (unsigned char) str1[i];
		ch2 = (unsigned char) str2[i];

		if (isalpha(ch1) && isalpha(ch2)) {
			ch1 = toupper(ch1);
			ch2 = toupper(ch2);
		}

		if (ch1 != ch2) return ch1 - ch2;
		if (ch1 == '\0') break;
	}

	return 0;
#endif
}

void*
uls_malloc(unsigned int n_bytes)
{
	void *ptr;

	if (n_bytes == 0) {
		ptr = NULL;
	} else {
		if ((ptr = malloc(n_bytes))==NULL)
			err_panic("malloc error, need some scheduling, ...");
	}

	return ptr;
}

void*
uls_mrealloc(void* ptr, unsigned int n_bytes)
{
	if (n_bytes == 0) { 
		if (ptr!=NULL) free(ptr);
		return NULL;
	}

	if (ptr == NULL) {
		ptr = malloc(n_bytes);
	} else {
		ptr = realloc(ptr, n_bytes);
	}

	if (ptr==NULL)
		err_panic("%s: malloc error, need some scheduling, ...", __FUNCTION__);

	return ptr;
}

void*
uls_mfree(void* ptr)
{
	if (ptr!=NULL) free(ptr);
	return NULL;
}

int
uls_range_of_bits(uls_uint32 n)
{
	int  i, i0 = -1;

	for (i=0; i<32; i++) {
		if (n & 0x01) i0 = i;
		n >>= 1;
	}

	return i0 + 1;
}

void
uls_clear_bits(uls_char_t* srcptr, uls_uint32 start_bit, uls_uint32 end_bit)
{
	uls_uint32  i1, j1, i2, j2, j;
	uls_byte_t  *dstptr;

	//
	//  This clear all the bits in [start_bit,end_bit] from 'dstptr'.
	//
	//     A0         A0+1        A0+2        A0+3        A0+4
	//     -------------------------------------------------
	//     |           |       #####|**********|###        |
	//     ------------|-------|----|----------|---|-------|-
	//                         P1                 P2
	//                         i1                 i2
	//                         j1                 j2
	//
	//   P1(i1,j1) == P(start_bit/8,start_bit%8)
	//   P2(i2,j2)
	//

	if (start_bit > end_bit) return;

	i1 = start_bit / 8;
	j1 = start_bit % 8;
	i2 = end_bit / 8;
	j2 = end_bit % 8;

	if (j1 > 0) {
		dstptr = srcptr + i1;

		if (i1 == i2) {
			for (j=j1; j<=j2; j++) {
				uls_clear_bit(j, dstptr);
			}
			return;
		}

		for (j=j1; j<8; j++) {
			uls_clear_bit(j, dstptr);
		}

		++i1;
		j1 = 0;
	}

	if (j2 == 7) {
		memset(srcptr + i1, 0x00, i2 - i1 + 1);
	} else {
		memset(srcptr + i1, 0x00, i2 - i1);
		dstptr = srcptr + i2;
		for (j=0; j<=j2; j++) {
			uls_clear_bit(j, dstptr);
		}
	}
}

static int
__find_first_1bit(uls_byte_t ch, int j1, int j2)
{
	int j;

	ch <<= j1;

	for (j=j1; j<=j2; j++) {
		if (ch & BYTE_LEFT_MOST_1) return j;
		ch <<= 1;
	}

	return -1;
}

int
uls_find_first_1bit(uls_char_t* srcptr,
	uls_uint32 start_bit, uls_uint32 end_bit, uls_uint32* found_bit)
{
	uls_byte_t  *dstptr;
	uls_uint32 i1, i2, i;
	int  j1, j2, j;

	//
	//  This find the first 1-bit in the range of [start_bit,end_bit] from 'srcptr'.
	//
	//     A0         A0+1        A0+2        A0+3        A0+4
	//     -------------------------------------------------
	//     |           |       #####|**********|###        |
	//     ------------|-------|----|----------|---|-------|-
	//                         P1                 P2
	//                         i1                 i2
	//                         j1                 j2
	//
	//   P1(i1,j1) == P(start_bit/8,start_bit%8)
	//   P2(i2,j2)
	//

	if (start_bit > end_bit) return 0; // NOT FOUND

	i1 = start_bit / 8;
	j1 = start_bit % 8;
	i2 = end_bit / 8;
	j2 = end_bit % 8;

	if (j1 > 0) {
		dstptr = srcptr + i1;

		if (i1 == i2) {
			if ((j=__find_first_1bit(*dstptr, j1, j2)) >= 0) {
				if (found_bit != NULL) *found_bit = i1*8 + j;
				return 1;
			}

			return 0; // NOT FOUND
		}

		if ((j=__find_first_1bit(*dstptr, j1, 7))>=0) {
			if (found_bit != NULL) *found_bit = i1*8 + j;
			return 1; // FOUND
		}

		++i1;
		j1 = 0;
	}

	for (i=i1; i < (j2 < 7) ? i2 : i2 + 1; i++) {
		if (srcptr[i] != 0) {
			j = __find_first_1bit(srcptr[i], 0, 7);
			if (found_bit != NULL) *found_bit = i*8 + j;
			return 1; // FOUND
		}
	}

	if (j2 < 7) {
		dstptr = srcptr + i2;
		if ((j=__find_first_1bit(*dstptr, 0, j2))>=0) {
			if (found_bit != NULL) *found_bit = i2*8 + j;
			return 1; // FOUND
		}
	}

	return 0; // NOT FOUND
}

uls_uch_t
uls_get_escape_char(char** p_lptr)
{
	char   *lptr = *p_lptr, ch;
	uls_uch_t ret_ch;
	int    i, n;

	if ((ch=*lptr) == '\0') {
		return '\\';
	}

	if (ch == 'x' || ch ==  'u') {
		n = (ch=='x') ? 2 : 4;
		ret_ch = 0;
		for (i=0; i<n; i++) {
			if (!isxdigit(ch=*++lptr)) {
				*p_lptr = lptr;
				return ret_ch;
			}

			ch = isdigit(ch) ? ch - '0' : 10 + (toupper(ch) - 'A');
			ret_ch = (ret_ch<<4) + ch;
		}

	} else if (isalpha(ch)) {
		switch (ch) {
		case 'b': ret_ch = '\b'; break;
		case 't': ret_ch = '\t'; break;
		case 'n': ret_ch = '\n'; break;
		case 'v': ret_ch = '\v'; break;
		case 'f': ret_ch = '\f'; break;
		case 'r': ret_ch = '\r'; break;
		default : ret_ch = ch;   break;
		}

	} else if (ch >= '0' && ch < '8') {
		ret_ch = ch - '0';
		for (i=1; i<3; i++) {
			if ((ch=*++lptr)<'0'|| ch>'7') {
				*p_lptr = lptr;
				return ret_ch;
			}
			ret_ch = (ret_ch<<3) + (ch-'0');
		}

	} else {
		ret_ch = ch;
	}

	*p_lptr = ++lptr;
	return ret_ch;
}

int
uls_get_escape_str(uls_char_t quote_ch, char* line, char* line2)
{
	char *lptr;
	int escape = 0, k=0;
	uls_uch_t  uch;
	char ch;

	for (lptr=line; ; ) {
		ch = *lptr;

		if (escape) {
			uch = uls_get_escape_char(&lptr);
			k += uls_encode_utf8(uch, (uls_char_t *) line2 + k);
			escape = 0;

		} else {
			if (ch==quote_ch) {
				break;
			}
			if (ch=='\\') {
				escape = 1;
			} else {
				line2[k++] = ch;
			}
			++lptr;
		}
	}

	line2[k] = '\0';
	return k;
}

void
uls_reverse_array(uls_char_t* ary, int n)
{
	uls_byte_t ch;
	int i, j;

	// swap numstr[0,.., n-1]
	for (i=0, j=n-1; i<n/2; i++, j--) {
		// swap numstr[i], numstr[n-i]
		ch = ary[i];
		ary[i] = ary[j];
		ary[j] = ch;
	}
}

void
get_current_time_yyyymmdd_hhmm(char* buf, int buf_siz)
{
	struct tm  *tm;
	time_t  curtim;

	curtim = time(NULL);
	tm = localtime(&curtim);

	uls_snprintf(buf, buf_siz, "%4d-%02d-%02d %02d:%02d",
		1900+tm->tm_year, 1+tm->tm_mon, tm->tm_mday,
		tm->tm_hour, tm->tm_min);
}

int
uls_readn(int fd, uls_char_t* ptr, int n)
{
	int	nleft, rc;

	for (nleft=n; nleft > 0; ) {
		if ((rc=uls_read(fd, ptr, nleft)) < 0) {
			if (errno==EINTR)  continue;
			return -1;
		} else if (rc==0) {
			return n - nleft;
		}

		nleft -= rc;
		ptr   += rc;
	}

	return n; 
}

int
uls_writen(int fd, uls_char_t* ptr, int n)
{
	int	 nleft, rc;

	for (nleft = n; nleft > 0; ) {
		if ((rc=uls_write(fd, ptr, nleft)) < 0) {
			if (errno==EINTR) continue; 
			break;
		} else if (rc==0) {
			return n - nleft;
		}

		nleft -= rc;
		ptr   += rc;
	}
	return n;
}

int
uls_readline(int fd, char* ptr, int n)
{
	int  i, rc;

	if (n < 1 || ptr==NULL || fd < 0) {
		err_log(" invalid param, n=%d", n);
		return -1;
	}

	--n;

	for (i=0; ; i++) {
		if (i >= n) {
//			err_log("too long line, truncating, ...");
			break;
		}

		if ((rc=uls_read(fd, ptr, 1)) < 0) {
			if (errno==EINTR)  continue;
			return -1;
		} else if (rc==0) {
			break;
		}

		if (*ptr++ == '\n') {
			++i;
			break;
		}
	}

	*ptr = '\0';
	return i; 
}

#if defined(HAVE_LIBPTHREAD)
void
uls_init_mutex(uls_mutex_t mtx)
{
	pthread_mutex_t *a_mtx = (pthread_mutex_t *) mtx;
	pthread_mutexattr_t attr;

	pthread_mutexattr_init(&attr);

	if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
		err_panic("error: fail to make the mutex recursive-one\n");

	if (pthread_mutex_init(a_mtx, &attr) != 0)
		err_panic("error: mutex init\n");

	pthread_mutexattr_destroy(&attr);
}

void
uls_deinit_mutex(uls_mutex_t mtx)
{
	if (pthread_mutex_destroy(mtx) != 0)
		err_panic("error: mutex destroy\n");
}

void uls_lock_mutex(uls_mutex_t mtx)
{
	if (pthread_mutex_lock(mtx) != 0)
		err_panic("error to pthread_mutex_lock");
}

void uls_unlock_mutex(uls_mutex_t mtx)
{
	if (pthread_mutex_unlock(mtx) != 0)
		err_panic("error to pthread_mutex_unlock");
}
#else
#if defined(WIN32)
void
uls_init_mutex(uls_mutex_t mtx)
{
	mtx->hndl = CreateMutex(NULL, FALSE, NULL);
	if (mtx->hndl == INVALID_HANDLE_VALUE)
		err_panic("error: mutex init\n");
}

void
uls_deinit_mutex(uls_mutex_t mtx)
{
	CloseHandle(mtx->hndl);
	mtx->hndl = INVALID_HANDLE_VALUE;
}

void uls_lock_mutex(uls_mutex_t mtx)
{
	DWORD dwWaitResult;

	dwWaitResult = WaitForSingleObject(mtx->hndl, INFINITE);

	if (dwWaitResult != WAIT_OBJECT_0) {
		err_panic("error: mutex lock\n");
	}
}

void uls_unlock_mutex(uls_mutex_t mtx)
{
	ReleaseMutex(mtx->hndl);
}
#else
void
uls_init_mutex(uls_mutex_t mtx)
{
}

void
uls_deinit_mutex(uls_mutex_t mtx)
{
}

void uls_lock_mutex(uls_mutex_t mtx)
{
}

void uls_unlock_mutex(uls_mutex_t mtx)
{
}
#endif
#endif
/*
 * i0 = getopts(argc, argv, "lqf:s:o:v", options);
 */

int
uls_getopts(int n_args, char* args[], char* optfmt, uls_optproc_t proc)
{
	char     *optarg, *optstr, *ptr;
	int      rc, opt, i, j, k;

	for (i=1; i<n_args; i=k+1) {
		if (args[i][0] != '-') break;

		optstr = &args[i][1];
		for (k=i, j=0; (opt=optstr[j]) != '\0'; ) {
			if (opt=='?') {
				return 0; // call usage();
			}

			if ( (ptr=strchr(optfmt, opt))==NULL)
				err_panic("%s: undefined option -%c", __FUNCTION__, opt);

			if ( ptr[1]==':') { /* the option 'opt' needs a arg-val */
				if (optstr[j+1]!='\0') {
					optarg = optstr + (j+1);
				} else if (k+1<n_args && args[k+1][0]!='-') {
					optarg = args[++k];
				} else {
					err_log("%s: option -%c requires an arg.", __FUNCTION__, opt);
					return -1;
				}

				if ((rc=proc(opt, optarg)) <= 0) {
					if (rc < 0)
						err_log("%s: semantic error in -%c %s.", __FUNCTION__, opt, optarg);
					return rc;
				}
				break;

			} else {
				optarg = "";
				if ((rc=proc(opt, optarg)) <= 0) {
					if (rc < 0)
						err_log("%s: semantic error in -%c.", __FUNCTION__, opt);
					return rc;
				}
				j++;
			}
		}
	}

	return i;
}

char*
is_cnst_suffix_contained(char* cstr_pool, const char* str, int l_str, char** p_end_ptr)
{
	char *ptr, *ret_ptr = NULL;
	int l;

	// assert: str != NULL
	if (l_str < 0) {
		if (str != NULL) {
			for (l_str=0; l_str<ULS_CNST_SUFFIX_MAXSIZ; l_str++) {
				if (str[l_str] == '\0') break;
			}
		} else {
			l_str = 0;
		}
	} else if (l_str > ULS_CNST_SUFFIX_MAXSIZ) {
		l_str = ULS_CNST_SUFFIX_MAXSIZ;
	}

	if (l_str == 0) {
		for  (ptr=cstr_pool; *ptr!='\0'; ptr+=l+1) {
			l = strlen(ptr);
		}
		if (p_end_ptr != NULL) *p_end_ptr = ptr;
		return NULL;
	}

	for (ptr=cstr_pool; *ptr!='\0'; ptr+=l+1) {
		if ((l=strlen(ptr)) <= l_str && memcmp(ptr, str, l) == 0) {
			ret_ptr = ptr;
			break;
		}
	}

	if (p_end_ptr != NULL) *p_end_ptr = ptr;
	return ret_ptr; // the ptr corresponding to 'str'
}

char*
find_cnst_suffix(char* cstr_pool, const char* str, int l_str, char** p_end_ptr)
{
	char *ptr, *ret_ptr = NULL;
	int l;

	for (ptr=cstr_pool; *ptr!='\0'; ptr+=l+1) {
		if ((l=strlen(ptr)) == l_str && memcmp(ptr, str, l_str) == 0) {
			ret_ptr = ptr;
			break;
		}
	}

	if (p_end_ptr != NULL) *p_end_ptr = ptr;
	return ret_ptr; // the ptr corresponding to 'str'
}

int
add_cnst_suffix(char* cnst_suffixes, const char* suffix)
{
	const char *srcptr;
	char ch, *dstptr, *ptr;
	int len, k;

	if (suffix == NULL || (len=strlen(suffix)) == 0 || len > ULS_CNST_SUFFIX_MAXSIZ)
		return 0;

	if (find_cnst_suffix(cnst_suffixes, suffix, len, &ptr) != NULL) {
		err_log("A duplicate const suffix, ignoring '%s', ...", suffix);
		return 0;
	}

	k = (int) (ptr - cnst_suffixes);
	if (ULS_CNST_SUFFIXES_MAXSIZ - k < len + 1) {
		return 0;
	}

	dstptr = cnst_suffixes + k;
	for (srcptr=suffix; (ch=*srcptr) != '\0'; srcptr++) {
		if (!isgraph(ch) || isdigit(ch)) return 0;
		*dstptr++ = ch;
	}

	*dstptr++ = '\0';
	*dstptr = '\0';

	return 1;
}
